home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Mac Mania 5
/
MacMania 5.toast
/
/
Internet software
/
NewsWatcher
/
NW Source
/
Source
/
header.c
< prev
next >
Wrap
Text File
|
1997-01-09
|
30KB
|
1,077 lines
/*----------------------------------------------------------------------------
header.c
This module exports several utility functions for working with news
and mail headers.
Copyright © 1994-1997, Northwestern University.
----------------------------------------------------------------------------*/
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include "glob.h"
#include "header.h"
#include "menus.h"
#include "newswatcher.h"
#include "strutil.h"
#include "net.h"
#include "memutil.h"
#include "ic.h"
typedef enum ELineBreakMapping {
kMapToSpace, /* map line breaks to space */
kMapToComma, /* map line breaks to comma */
kMapToCR, /* map line breaks to CR */
kNoMapCR /* no line break mapping */
} ELineBreakMapping;
/*----------------------------------------------------------------------------
LocateHeaderLine
Locate a header line.
Entry: text = handle to header text.
hdrEnd = length of header text.
key = C-format header to locate, not including
the terminating ":".
Exit: function result = true if header found, else false.
*start = offset in text of start of header contents.
*len = length of header contents.
This function only returns the first part of "folded" header lines.
----------------------------------------------------------------------------*/
static Boolean LocateHeaderLine (Handle text, long hdrEnd, char *key, long *start, long *len)
{
long keyLen;
char *p, *pEnd, *q;
keyLen = strlen(key);
p = *text;
pEnd = *text + hdrEnd;
while (p < pEnd) {
if (MyStrNEqual(p, key, keyLen)) {
p += keyLen;
while (p < pEnd && isLWSP(*p)) p++;
if (*p == ':') {
p++;
while (p < pEnd && isLWSP(*p)) p++;
q = p;
while (q < pEnd && *q != CR) q++;
q--;
while (q >= p && isLWSP(*q)) q--;
q++;
*start = p - *text;
*len = q - p;
return true;
}
}
while (p < pEnd && *p != CR) p++;
p++;
}
return false;
}
/*----------------------------------------------------------------------------
LocateArticleHeaderLine
Locate a message header line in an article.
Entry: text = handle to article text.
key = C-format header to locate, not including
the terminating ":".
Exit: function result = true if header found, else false.
*start = offset in text of start of header contents.
*len = length of header contents.
This function only returns the first part of "folded" header lines.
The caller should call LocateContinuationHeaderLine below to locate
the continuation lines.
----------------------------------------------------------------------------*/
static Boolean LocateArticleHeaderLine (Handle text, char *key, long *start, long *len)
{
long hdrEnd;
hdrEnd = Munger(text, 0, CRCR, 2, nil, 0);
if (hdrEnd < 0) hdrEnd = MyGetHandleSize(text);
return LocateHeaderLine(text, hdrEnd, key, start, len);
}
/*----------------------------------------------------------------------------
LocateContinuationHeaderLine
Locate a continuation message header line in an article.
Entry: text = handle to article text.
*start = offset in text of first character following end of
previous line in "folded" header line.
Exit: function result = true if continuation header line found, else false.
*start = offset in text of start of continuation header line.
*len = length of continuation header line.
----------------------------------------------------------------------------*/
static Boolean LocateContinuationHeaderLine (Handle text, long *start, long *len)
{
char *p, *pEnd, *q;
p = *text + *start;
pEnd = *text + MyGetHandleSize(text);
while (p < pEnd && isLWSP(*p)) p++;
if (p >= pEnd || *p != CR) return false;
p++;
if (p >= pEnd || !isLWSP(*p)) return false;
while (p < pEnd && isLWSP(*p)) p++;
q = p;
while (q < pEnd && *q != CR) q++;
q--;
while (q >= p && isLWSP(*q)) q--;
q++;
*start = p - *text;
*len = q - p;
return true;
}
/*----------------------------------------------------------------------------
MakePathHeader
Make a "Path" header line.
Exit: function result = error code.
path = C-format "Path" header line.
----------------------------------------------------------------------------*/
static OSErr MakePathHeader (char path[261])
{
OSErr err = noErr;
err = NetGetMyName(path);
if (err == noErr) {
strcat(path, "!user");
} else if (err == userCanceledErr) {
return err;
} else {
strcpy(path, "NewsWatcher!user");
}
return noErr;
}
/*----------------------------------------------------------------------------
MakeDateHeader
Make a "Date" header line.
Exit: date = C-format "Date" header line. Empty string if can't get
machine location.
----------------------------------------------------------------------------*/
static void MakeDateHeader (CStr255 date)
{
static char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
static char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
DateTimeRec dtRec;
MachineLocation loc;
long gmtDelta;
char gmtSign;
short gmtHours;
short gmtMinutes;
ReadLocation(&loc);
*date = 0;
if (loc.latitude == 0 && loc.longitude == 0 && loc.u.gmtDelta == 0) return;
gmtDelta = loc.u.gmtDelta & 0x00ffffff;
if ((gmtDelta >> 23) & 1) gmtDelta |= 0xff000000;
if (gmtDelta < 0) {
gmtSign = '-';
gmtDelta = -gmtDelta;
} else {
gmtSign = '+';
}
gmtHours = gmtDelta / 3600;
gmtMinutes = (gmtDelta % 3600) / 60;
GetTime(&dtRec);
sprintf(date, "%s, %.2d %s %.4d %.2d:%.2d:%.2d %c%.2d%.2d",
days[dtRec.dayOfWeek - 1],
dtRec.day,
months[dtRec.month - 1],
dtRec.year,
dtRec.hour,
dtRec.minute,
dtRec.second,
gmtSign,
gmtHours,
gmtMinutes
);
}
/*----------------------------------------------------------------------------
MakeFromHeader
Make a "From" header line.
Exit: from = C-format "From" header line.
----------------------------------------------------------------------------*/
void MakeFromHeader (char from[514])
{
MyICReadSharedPrefs(kICRealName);
MyICReadSharedPrefs(kICEmail);
if (*gPrefs.fullName == 0) {
strcpy(from, gPrefs.emailAddress);
} else {
sprintf(from, "%s (%s)", gPrefs.emailAddress, gPrefs.fullName);
}
}
/*----------------------------------------------------------------------------
MakeMsgIdHeader
Make a "Message-ID" header line.
Exit: function result = error code.
id = message id header line, or empty string if error.
----------------------------------------------------------------------------*/
static OSErr MakeMsgIdHeader (char id[512])
{
static DateTimeRec prevDtRec = {0, 0, 0, 0, 0, 0, 0};
static short uniqueWithinSecond = 0;
DateTimeRec dtRec;
CStr255 hostName;
OSErr err = noErr;
char *p;
short len;
MyICReadSharedPrefs(kICEmail);
*id = 0;
if (*gPrefs.emailAddress == 0) return noErr;
err = NetGetMyName(hostName);
if (err != noErr) {
if (err == userCanceledErr) return err;
err = NetGetMyAddrStr(hostName);
if (err != noErr) return err;
}
if (*hostName == 0) return noErr;
for (p = gPrefs.emailAddress; *p != 0 && *p != '@' && *p != '%' &&
*p != ' ' && *p != '<' && *p != '>' && *p != ','; p++) /* do nothing */;
len = p - gPrefs.emailAddress;
GetTime(&dtRec);
if (prevDtRec.day == dtRec.day && prevDtRec.month == dtRec.month &&
prevDtRec.year == dtRec.year && prevDtRec.hour == dtRec.hour &&
prevDtRec.minute == dtRec.minute && prevDtRec.second == dtRec.second)
{
uniqueWithinSecond++;
} else {
prevDtRec = dtRec;
uniqueWithinSecond = 1;
}
sprintf(id, "<%.*s-%.2d%.2d%.2d%.2d%.2d%.2d%.4d@%s>",
len, gPrefs.emailAddress, dtRec.day, dtRec.month,
dtRec.year%100, dtRec.hour, dtRec.minute, dtRec.second,
uniqueWithinSecond, hostName);
return noErr;
}
/*----------------------------------------------------------------------------
AddHeader
Add a single header line to a header under construction.
Entry: key = C-format header name string, not including the
terminating colon.
hdrContents = pointer to header contents. Nil or empty if none.
hdrContentsLen = length of header contents.
hdr = handle to header under construction.
*hdrNext = index in header to store next line.
stripWhiteSpace = true to strip white space from header contents.
lineBreakMapping = line break mapping in header contents.
Exit: function result = error code.
*hdrNext updated.
----------------------------------------------------------------------------*/
static OSErr AddHeader (char *key, char *hdrContents, long hdrContentsLen,
Handle hdr, long *hdrNext, Boolean stripWhiteSpace,
ELineBreakMapping lineBreakMapping)
{
long next, size, len, keyLen;
OSErr err = noErr;
char *start, *end, *p, *q;
if (hdrContents == nil) return noErr;
start = hdrContents;
end = start + hdrContentsLen - 1;
while (start <= end && isLWSPorCR(*start)) start++;
while (start <= end && isLWSPorCR(*end)) end--;
hdrContentsLen = end - start + 1;
if (hdrContentsLen <= 0) return noErr;
keyLen = strlen(key);
len = keyLen + hdrContentsLen + 3;
next = *hdrNext;
size = MyGetHandleSize(hdr);
if (next + len > size) {
err = MySetHandleSize(hdr, next + len + 1000);
if (err != noErr) return err;
}
q = *hdr + next;
BlockMoveData(key, q, keyLen);
q += keyLen;
BlockMoveData(": ", q, 2);
q += 2;
p = start;
while (p <= end) {
if (stripWhiteSpace && isLWSP(*p)) {
p++;
} else if (*p == CR && lineBreakMapping != kNoMapCR) {
q--;
while (q >= *hdr && isLWSPorCR(*q)) q--;
q++;
p++;
while (p <= end && isLWSPorCR(*p)) p++;
switch (lineBreakMapping) {
case kMapToSpace:
*q++ = ' ';
break;
case kMapToComma:
*q++ = ',';
break;
case kMapToCR:
*q++ = CR;
break;
}
} else {
*q++ = *p++;
}
}
*q++ = CR;
*hdrNext = q - *hdr;
return noErr;
}
/*----------------------------------------------------------------------------
AddHeaderCString
Add a single C-format header line to a header under construction.
Entry: key = C-format header name string, not including the
terminating colon.
hdrContents = C-format header contents. Nil or empty if none.
hdr = handle to header under construction.
*hdrNext = index in header to store next line.
stripWhiteSpace = true to strip white space from header contents.
lineBreakMapping = line break mapping in header contents.
Exit: function result = error code.
*hdrNext updated.
----------------------------------------------------------------------------*/
static OSErr AddHeaderCString (char *key, char *hdrContents, Handle hdr, long *hdrNext,
Boolean stripWhiteSpace, ELineBreakMapping lineBreakMapping)
{
return AddHeader(key, hdrContents, strlen(hdrContents), hdr, hdrNext,
stripWhiteSpace, lineBreakMapping);
}
/*----------------------------------------------------------------------------
AddHeaderHandle
Add a single header line contained in a relocatable block to a header
under construction.
Entry: key = C-format header name string, not including the
terminating colon.
hdrContents = handle to header contents. Nil or empty if none.
hdr = handle to header under construction.
*hdrNext = index in header to store next line.
stripWhiteSpace = true to strip white space from header contents.
lineBreakMapping = line break mapping in header contents.
Exit: function result = error code.
*hdrNext updated.
----------------------------------------------------------------------------*/
static OSErr AddHeaderHandle (char *key, Handle hdrContents, Handle hdr, long *hdrNext,
Boolean stripWhiteSpace, ELineBreakMapping lineBreakMapping)
{
OSErr err = noErr;
char state;
if (hdrContents == nil) return noErr;
state = MyHGetState(hdrContents);
MyHLock(hdrContents);
err = AddHeader(key, *hdrContents, MyGetHandleSize(hdrContents), hdr, hdrNext,
stripWhiteSpace, lineBreakMapping);
MyHSetState(hdrContents, state);
return err;
}
/*----------------------------------------------------------------------------
AddExtraHeaderLines
Add extra header lines, overriding any earlier ones.
Entry: extras = handle to extra header lines. Nil or empty if none.
hdr = handle to header under construction.
*hdrNext = index in header to store next line.
Exit: function result = error code.
*hdrNext updated.
----------------------------------------------------------------------------*/
static OSErr AddExtraHeaderLines (Handle extras, Handle hdr, long *hdrNext)
{
long keyLen, contentLen;
OSErr err = noErr;
char *p, *pEnd, *q, *r, *s;
CStr255 key;
long start, len;
char state;
state = MyHGetState(extras);
if (extras == nil) return noErr;
MyHLock(extras);
p = *extras;
pEnd = p + MyGetHandleSize(extras);
while (p < pEnd) {
q = p;
while (q < pEnd && *q != CR && *q != ':') q++;
if (q >= pEnd || *q == CR) {
p = q+1;
continue;
}
s = r = q+1;
while (true) {
while (r < pEnd && *r != CR) r++;
if (r >= pEnd) break;
if (!isLWSP(*(r+1))) break;
r++;
}
q--;
while (q >= p && isLWSP(*q)) q--;
q++;
if (p < q) {
keyLen = q - p;
if (keyLen < 256) {
BlockMoveData(p, key, keyLen);
key[keyLen] = 0;
while (s < pEnd && isLWSP(*s)) s++;
contentLen = r - s;
if (contentLen > 0) {
if (LocateHeaderLine(hdr, *hdrNext, key, &start, &len)) {
Munger(hdr, start, nil, len, s, contentLen);
err = MemError();
if (err != noErr) goto exit;
*hdrNext += contentLen - len;
} else {
err = AddHeader(key, s, contentLen, hdr, hdrNext, false, kNoMapCR);
if (err != noErr) goto exit;
}
}
}
}
p = r+1;
}
exit:
MyHSetState(extras, state);
return err;
}
/*----------------------------------------------------------------------------
HeaderLineIsEmpty
Check for an empty header line.
Entry: hdr = handle to header line.
Exit: function result = true if header line is empty (nil, or consists
of only space, CR, and tab characters).
----------------------------------------------------------------------------*/
Boolean HeaderLineIsEmpty (Handle hdr)
{
char *p, *pEnd;
if (hdr == nil) return true;
p = *hdr;
pEnd = p + MyGetHandleSize(hdr);
while (p < pEnd && isLWSPorCR(*p)) p++;
return p >= pEnd;
}
/*----------------------------------------------------------------------------
MakeNewsHeader
Make a news article header.
Entry: newsgroups = Handle to "Newsgroups" header contents.
subject = Handle to "Subject" header contents.
replyto = Handle to "Reply-To" header contents. Nil or empty if none.
followupto = Handle to "Followup-To" header contents. Nil or empty if none.
keywords = Handle to "Keywords" header contents. Nil or empty if none.
distribution = Handle to "Distribution" header contents. Nil or empty if none.
extras = Handle to extra header lines. Nil or empty if none.
control = Handle to "Control" header contents. Nil or empty if none.
references = Handle to "References" header contents. Nil or empty if none.
Exit: function result = error code.
header = handle to header.
The constructed header includes a blank line at the end (CRCR).
----------------------------------------------------------------------------*/
OSErr MakeNewsHeader (Handle newsgroups, Handle subject, Handle replyto,
Handle followupto, Handle keywords, Handle distribution, Handle extras,
Handle control, Handle references, Handle *header)
{
Handle hdr;
OSErr err = noErr;
long hdrNext;
CStr255 date;
char path[261];
char from[514];
char messageid[512];
MyICReadSharedPrefs(kICOrganization);
err = MyNewHandle(1000, &hdr);
if (err != noErr) return err;
hdrNext = 0;
err = MakePathHeader(path);
if (err != noErr) goto exit;
MakeDateHeader(date);
MakeFromHeader(from);
err = MakeMsgIdHeader(messageid);
if (err != noErr) goto exit;
err = AddHeaderCString("Path", path, hdr, &hdrNext, false, kMapToSpace);
if (err != noErr) goto exit;
err = AddHeaderCString("Date", date, hdr, &hdrNext, false, kMapToSpace);
if (err != noErr) goto exit;
err = AddHeaderCString("From", from, hdr, &hdrNext, false, kMapToSpace);
if (err != noErr) goto exit;
err = AddHeaderHandle("Newsgroups", newsgroups, hdr, &hdrNext, true, kMapToComma);
if (err != noErr) goto exit;
err = AddHeaderHandle("Followup-To", followupto, hdr, &hdrNext, true, kMapToComma);
if (err != noErr) goto exit;
err = AddHeaderHandle("Reply-To", replyto, hdr, &hdrNext, false, kMapToComma);
if (err != noErr) goto exit;
err = AddHeaderHandle("Distribution", distribution, hdr, &hdrNext, false, kMapToSpace);
if (err != noErr) goto exit;
err = AddHeaderHandle("Keywords", keywords, hdr, &hdrNext, false, kMapToSpace);
if (err != noErr) goto exit;
err = AddHeaderHandle("Subject", subject, hdr, &hdrNext, false, kMapToSpace);
if (err != noErr) goto exit;
err = AddHeaderCString("Message-ID", messageid, hdr, &hdrNext, false, kMapToSpace);
if (err != noErr) goto exit;
err = AddHeaderHandle("References", references, hdr, &hdrNext, false, kNoMapCR);
if (err != noErr) goto exit;
err = AddHeaderCString("Organization", gPrefs.organization, hdr, &hdrNext, false, kMapToSpace);
if (err != noErr) goto exit;
err = AddHeaderHandle("Control", control, hdr, &hdrNext, false, kMapToSpace);
if (err != noErr) goto exit;
err = AddExtraHeaderLines(extras, hdr, &hdrNext);
if (err != noErr) goto exit;
err = MySetHandleSize(hdr, hdrNext+1);
if (err != noErr) goto exit;
*(*hdr + hdrNext) = CR;
*header = hdr;
return noErr;
exit:
MyDisposeHandle(hdr);
return err;
}
/*----------------------------------------------------------------------------
MakeMailHeader
Make a mail message header.
Entry: subject = Handle to "Subject" header contents.
to = Handle to "To" header contents. Nil or empty if none.
cc = Handle to "Cc" header contents. Nil or empty if none.
bcc = Handle to "Bcc" header contents. Nil or empty if none.
from = Handle to "From" header contents. Nil or empty to use
gPrefs.emailAddress (gPrefs.fullName).
copySelf = true to include self in "bcc" header line, or in "to"
header line if to, cc, and bcc are all empty.
replyto = Handle to "Reply-To" header contents. Nil or empty if none.
keywords = Handle to "Keywords" header contents. Nil or empty if none.
newsgroups = Handle to "Newsgroups" header contents. Nil or empty if none.
followupto = Handle to "Followup-To" header contents. Nil or empty if none.
distribution = Handle to "Distribution" header contents. Nil or empty if none.
extras = Handle to extra header lines. Nil or empty if none.
references = Handle to "References" header contents. Nil or empty if none.
Exit: function result = error code.
header = handle to header.
The constructed header includes a blank line at the end (CRCR).
----------------------------------------------------------------------------*/
OSErr MakeMailHeader (Handle subject, Handle to, Handle cc, Handle bcc, Handle from,
Boolean copySelf, Handle replyto, Handle keywords, Handle extras,
Handle newsgroups, Handle followupto, Handle distribution,
Handle references, Handle *header)
{
Handle hdr = nil;
Handle xbcc = nil;
Handle xto = nil;
Boolean disposeXbcc = false;
Boolean disposeXto = false;
OSErr err = noErr;
long hdrNext;
CStr255 date;
char fromHdr[514];
long bccLen, selfLen, xbccLen;
char *q;
MyICReadSharedPrefs(kICOrganization);
MyICReadSharedPrefs(kICEmail);
err = MyNewHandle(1000, &hdr);
if (err != noErr) return err;
hdrNext = 0;
MakeDateHeader(date);
xto = to;
xbcc = bcc;
if (copySelf) {
selfLen = strlen(gPrefs.emailAddress);
if (HeaderLineIsEmpty(to) && HeaderLineIsEmpty(cc)) {
err = MyNewHandle(selfLen, &xto);
if (err != noErr) goto exit;
disposeXto = true;
BlockMoveData(gPrefs.emailAddress, *xto, selfLen);
} else {
bccLen = bcc == nil ? 0 : MyGetHandleSize(bcc);
xbccLen = bccLen + selfLen;
if (bcc != nil) xbccLen++;
err = MyNewHandle(xbccLen, &xbcc);
if (err != noErr) goto exit;
disposeXbcc = true;
q = *xbcc;
if (bcc != nil) {
BlockMoveData(*bcc, *xbcc, bccLen);
q += bccLen;
*q++ = ',';
}
BlockMoveData(gPrefs.emailAddress, q, selfLen);
}
}
err = AddHeaderCString("Date", date, hdr, &hdrNext, false, kMapToSpace);
if (err != noErr) goto exit;
if (HeaderLineIsEmpty(from)) {
MakeFromHeader(fromHdr);
err = AddHeaderCString("From", fromHdr, hdr, &hdrNext, false, kMapToSpace);
} else {
err = AddHeaderHandle("From", from, hdr, &hdrNext, false, kMapToSpace);
}
if (err != noErr) goto exit;
err = AddHeaderHandle("To", xto, hdr, &hdrNext, false, kMapToComma);
if (err != noErr) goto exit;
err = AddHeaderHandle("Cc", cc, hdr, &hdrNext, false, kMapToComma);
if (err != noErr) goto exit;
err = AddHeaderHandle("Bcc", xbcc, hdr, &hdrNext, false, kMapToComma);
if (err != noErr) goto exit;
err = AddHeaderHandle("Reply-To", replyto, hdr, &hdrNext, false, kMapToComma);
if (err != noErr) goto exit;
err = AddHeaderHandle("Subject", subject, hdr, &hdrNext, false, kMapToSpace);
if (err != noErr) goto exit;
err = AddHeaderHandle("Keywords", keywords, hdr, &hdrNext, false, kMapToSpace);
if (err != noErr) goto exit;
err = AddHeaderHandle("Newsgroups", newsgroups, hdr, &hdrNext, true, kMapToComma);
if (err != noErr) goto exit;
err = AddHeaderHandle("Followup-To", followupto, hdr, &hdrNext, false, kMapToComma);
if (err != noErr) goto exit;
err = AddHeaderHandle("Distribution", distribution, hdr, &hdrNext, false, kMapToSpace);
if (err != noErr) goto exit;
err = AddHeaderHandle("References", references, hdr, &hdrNext, false, kNoMapCR);
if (err != noErr) goto exit;
err = AddHeaderCString("Organization", gPrefs.organization, hdr, &hdrNext, false, kMapToSpace);
if (err != noErr) goto exit;
err = AddExtraHeaderLines(extras, hdr, &hdrNext);
if (err != noErr) goto exit;
err = MySetHandleSize(hdr, hdrNext+1);
if (err != noErr) goto exit;
*(*hdr + hdrNext) = CR;
if (disposeXto) MyDisposeHandle(xto);
if (disposeXbcc) MyDisposeHandle(xbcc);
*header = hdr;
return noErr;
exit:
MyDisposeHandle(hdr);
if (disposeXto) MyDisposeHandle(xto);
if (disposeXbcc) MyDisposeHandle(xbcc);
return err;
}
/*----------------------------------------------------------------------------
FindHeaderCString
Find and extract a message header from an article and return it as a
C-format string.
Entry: text = handle to article text.
key = C-format header to locate, not including
the terminating ":".
maxLen = maximum length of returned header contents, including
C-format terminating 0 byte.
Exit: function result = true if header found, else false.
contents = extracted C-format header contents, with leading and
trailing white space deleted.
If the header content string is longer than the maximum length, it is
truncated to the maximum length.
----------------------------------------------------------------------------*/
Boolean FindHeaderCString (Handle text, char *key, char *contents,
long maxLength)
{
long start, len;
char *q;
*contents = 0;
q = contents;
if (!LocateArticleHeaderLine(text, key, &start, &len)) return false;
while (true) {
if (len >= maxLength) len = maxLength - 1;
BlockMoveData(*text + start, q, len);
start += len;
q += len;
maxLength -= len;
if (maxLength <= 1) break;
if (!LocateContinuationHeaderLine(text, &start, &len)) break;
*q++ = ' ';
maxLength--;
}
*q = 0;
return true;
}
/*----------------------------------------------------------------------------
FindHeaderHandle
Find and extract a message header from an article and return it in a
relocatable block.
Entry: text = handle to article text.
key = C-format header to locate, not including
the terminating ":".
Exit: function result = error code.
contents = handle to extracted header contents, with leading and
trailing white space deleted, or nil if header not found.
----------------------------------------------------------------------------*/
OSErr FindHeaderHandle (Handle text, char *key, Handle *contents)
{
long start, firstLineStart, firstLineLen, len, totalLen;
Handle h;
char *q;
OSErr err = noErr;
*contents = nil;
if (!LocateArticleHeaderLine(text, key, &start, &len)) return noErr;
totalLen = 0;
firstLineStart = start;
firstLineLen = len;
while (true) {
totalLen += len;
start += len;
if (!LocateContinuationHeaderLine(text, &start, &len)) break;
totalLen += 1;
}
err = MyNewHandle(totalLen, &h);
if (err != noErr) return err;
MyHLock(h);
q = *h;
start = firstLineStart;
len = firstLineLen;
while (true) {
BlockMoveData(*text + start, q, len);
start += len;
q += len;
if (!LocateContinuationHeaderLine(text, &start, &len)) break;
*q++ = ' ';
}
MyHUnlock(h);
*contents = h;
return noErr;
}
/*----------------------------------------------------------------------------
FormatAuthorName
Format an author name.
Entry: name = C-format "From" header contents.
Exit: name = formatted name.
If the "From" line is in the form "address (name)", "name" is returned.
If the "From" line is in the form "name <address>", "name" is returned.
Otherwise the "From" line is unchanged.
Note that the name is formatted in place, destroying the original
string, and that the formatted name is always shorter or the same length
as the orginal string.
----------------------------------------------------------------------------*/
void FormatAuthorName (char *name)
{
char *p, *start, *end;
short len, parenLevel;
start = name;
end = name + strlen(name) - 1;
p = name;
while (*p != 0 && *p != '(' && *p != '<') p++;
if (*p == '(') {
/* format = "address (name)" */
p++;
start = p;
parenLevel = 1;
while (*p != 0) {
if (*p == '(') {
parenLevel++;
} else if (*p == ')') {
parenLevel--;
if (parenLevel == 0) break;
}
p++;
}
end = p-1;
} else if (*p == '<') {
/* format = "name <address>" */
end = p-1;
} else {
return;
}
if (start <= end) {
while (start < end && (isLWSP(*start) || *start == '"')) start++;
while (start < end && (isLWSP(*end) || *end == '"')) end--;
len = end - start + 1;
if (len > 0) {
BlockMoveData(start, name, len);
name[len] = 0;
}
}
}
/*----------------------------------------------------------------------------
FindBody
Find the beginning of the body of an article.
Entry: text = handle to article text.
Exit: function result = offset in article of first character of
article body, or length of article if article has no body.
----------------------------------------------------------------------------*/
long FindBody (Handle text)
{
char *p, *pEnd;
long length;
length = MyGetHandleSize(text);
p = *text;
pEnd = p + length;
while (p < pEnd) {
if (*p == CR && *(p+1) == CR) break;
p++;
}
p += 2;
while (p < pEnd && *p == CR) p++;
return p < pEnd ? p-*text : length;
}
/*----------------------------------------------------------------------------
DeleteHeaderLine
Delete a header line.
Entry: text = handle to text.
key = keyword of header line to delete.
----------------------------------------------------------------------------*/
void DeleteHeaderLine (Handle text, char *key)
{
long textLen, firstStart, len, start;
textLen = MyGetHandleSize(text);
if (!LocateHeaderLine(text, textLen, key, &firstStart, &len)) return;
start = firstStart + len;
while (LocateContinuationHeaderLine(text, &start, &len)) start = start + len;
firstStart--;
while (firstStart >= 0 && *(*text + firstStart) != CR) firstStart--;
firstStart++;
while (start < textLen && *(*text + start) != CR) start++;
if (start < textLen) {
start++;
} else if (firstStart > 0) {
firstStart--;
}
len = start - firstStart;
Munger(text, firstStart, nil, len, "", 0);
}
/*----------------------------------------------------------------------------
ParseRe
Parse the "Re:" portion of a followup subject header line.
Entry: subject = pointer to subject header line.
len = length of subject header line.
Exit: function result = length of initial "Re:" of header line.
The "Re:" portion of a subject line is the inital segment of the line
which consists of any sequence of the following: "Re:", "Re(n):",
"Re[n]:", and "Re^n:".
----------------------------------------------------------------------------*/
long ParseRe (char *subject, long len)
{
char *p, *pEnd, *q;
char closeBracket;
p = subject;
pEnd = p + len;
while (p < pEnd) {
q = p;
if (q + 2 > pEnd) break;
if (!MyStrNEqual(q, "Re", 2)) break;
q += 2;
while (q < pEnd && isLWSP(*q)) q++;
if (q >= pEnd) break;
if (*q == '(' || *q == '[') {
closeBracket = *q == '(' ? ')' : ']';
q++;
while (q < pEnd && isLWSP(*q)) q++;
while (q < pEnd && isdigit(*q)) q++;
while (q < pEnd && isLWSP(*q)) q++;
if (q >= pEnd) break;
if (*q != closeBracket) break;
q++;
if (q >= pEnd) break;
} else if (*q == '^') {
q++;
while (q < pEnd && isLWSP(*q)) q++;
while (q < pEnd && isdigit(*q)) q++;
if (q >= pEnd) break;
}
while (q < pEnd && isLWSP(*q)) q++;
if (q >= pEnd) break;
if (*q != ':') break;
p = q + 1;
while (p < pEnd && isLWSP(*p)) p++;
}
return p - subject;
}
/*----------------------------------------------------------------------------
StringIsValidEmailAddress
Check a string to see if it is a syntactically valid email address (a
non-empty string, followed by '@', followed by a non-empty string,
followed by '.', followed by a non-empty string).
Entry: str = C-format string.
Exit: function result = true if valid email address.
----------------------------------------------------------------------------*/
Boolean StringIsValidEmailAddress (char *str)
{
char *p, *q;
p = str;
q = p;
while (*q != 0 && *q != '@') q++;
if (q == p || *q == 0) return false;
p = q = q+1;
while (*q != 0 && *q != '.') q++;
if (q == p || *q == 0) return false;
p = q = q+1;
while (*q != 0) q++;
return q > p;
}